package com.zbl.service;

import com.gargoylesoftware.htmlunit.*;
import com.gargoylesoftware.htmlunit.html.*;
import com.xuxueli.crawler.util.UrlUtil;
import com.zbl.config.Constants;
import com.zbl.dto.LoginParam;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.HashMap;
import java.util.Map;

/**
 * @author glory
 * @date 2020/4/29 11:32
 * @description
 **/
public abstract class AbstractLoginService {
    private static final Logger logger = LoggerFactory.getLogger(AbstractLoginService.class);
    /**
     * webclient
     */
    protected Map<String, WebClient> webClientMaps = new HashMap();
    /**
     * 登陆页面
     */
    protected Map<String, HtmlPage> loginPageMaps = new HashMap();

    /**
     * 加载登陆页面
     *
     * @param param
     * @return
     */
    public WebClient load(LoginParam param) {
        if (!UrlUtil.isUrl(param.getLoginUrl())) {
            return null;
        }
        //配置webClient
        WebClient webClient = new WebClient(BrowserVersion.CHROME);
        //ajax
        webClient.setAjaxController(new NicelyResynchronizingAjaxController());
        //支持js
        webClient.getOptions().setJavaScriptEnabled(true);
        //忽略js错误
        webClient.getOptions().setThrowExceptionOnScriptError(false);
        //忽略css错误
        webClient.setCssErrorHandler(new SilentCssErrorHandler());
        //不执行CSS渲染
        webClient.getOptions().setCssEnabled(false);

        //允许重定向
        webClient.getOptions().setRedirectEnabled(true);
        //允许cookie
        webClient.getCookieManager().setCookiesEnabled(true);

        //指定是否在出现故障状态代码时抛出异常
        webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
        //启用/禁用“不跟踪”支持
        webClient.getOptions().setDoNotTrackEnabled(false);
        //如果设置为true，客户机将接受与任何主机的连接，而不管它们是否有有效证书
        webClient.getOptions().setUseInsecureSSL(true);

        //设置该程序的超时时间。
        webClient.getOptions().setTimeout(param.getTimeoutMillis());
        //设置js执行的超时时间。
        webClient.setJavaScriptTimeout(param.getTimeoutMillis());
        //异步JS执行需要耗时,所以这里线程要阻塞xx秒,等待异步JS执行结束
        webClient.waitForBackgroundJavaScript(15000);

        // 代理
        if (param.getProxy() != null) {
            InetSocketAddress address = (InetSocketAddress) param.getProxy().address();
            boolean isSocks = param.getProxy().type() == Proxy.Type.SOCKS;
            webClient.getOptions().setProxyConfig(new ProxyConfig(address.getHostName(), address.getPort(), isSocks));
        }

        return webClient;
    }

    /**
     * 登录
     *
     * @param loginParam
     * @return
     */
    public HtmlPage login(LoginParam loginParam) throws IOException, InterruptedException {
        System.out.println(loginParam);
        WebClient webClient = this.webClientMaps.get(loginParam.getLoginUrl());
        HtmlPage loginPage = this.loginPageMaps.get(loginParam.getLoginUrl());
        /**
         * webClient、loginPage
         * 说明不需要验证码登录
         */
        if (webClient == null || loginPage == null) {
            webClient = load(loginParam);
            try {
                /*HtmlPage preLoginPage = webClient.getPage(loginParam.getLoginUrl());
//                System.out.println(">>>>>>>>>>>>> " + preLoginPage.asXml());
                HtmlElement loginBtn = (HtmlElement) preLoginPage.getElementById("js-signin-btn");
                System.out.println(">>>>>>>>>>>>> " + loginBtn.asXml());

                loginPage = loginBtn.click();*/
                loginPage = webClient.getPage(loginParam.getLoginUrl());
                webClientMaps.put(loginParam.getLoginUrl(), webClient);
            } catch (Exception e) {
                //抛出异常是关闭WebClient,不要在finally代码块中关闭，这样的话下面页面中的操作会失效。
                webClient.close();
                logger.error(e.getMessage());
                e.printStackTrace();
            }
        }
        //获取用户名、密码、验证码输入框HTML DOM - 元素
       /* HtmlInput loginNameInput = (HtmlInput) loginPage.getElementById(loginParam.getUserNameInputId());
        HtmlInput passwordInput = (HtmlInput) loginPage.getElementById(loginParam.getPasswordInputId());*/
//        System.out.println(">>>>>>>>>>>>>>>>>loginPage " + loginPage.asXml());
        HtmlInput loginNameInput = loginPage.querySelector(loginParam.getUserNameInputId());
        HtmlInput passwordInput = loginPage.querySelector(loginParam.getPasswordInputId());
        System.out.println(">>>>>>>>>>>>>>" + loginNameInput.asXml());
        System.out.println(">>>>>>>>>>>>>>" + passwordInput.asXml());
        HtmlInput checkNumInput = null;
        /**
         * 有些网站登录不需要输入验证码
         */
        if (StringUtils.isNotBlank(loginParam.getCheckCodeInputId())) {
            checkNumInput = (HtmlInput) loginPage.getElementById(loginParam.getCheckCodeInputId());
        }
        //为用户名、密码、验证码输入框HTML DOM - 元素赋值
        loginNameInput.setValueAttribute(loginParam.getLoginName());
        passwordInput.setValueAttribute(loginParam.getPassword());
        if (checkNumInput != null) {
            checkNumInput.setValueAttribute(loginParam.getCheckCode());
        }
        HtmlPage newPage;

        //有很多网站的提交按钮不一定是form-><input type="submit">标准提交form,大部分提交按钮可能是<input type="button">、<button/>、<a href="javascript:void(0);">等
        //书统一使用父类HtmlElement来接收，而不是具体的实现类HtmlSubmitInput、HtmlButtonInput、HtmlButton、HtmlAnchor
        HtmlElement loginBtn = loginPage.querySelector(loginParam.getLoginButtonId());
        System.out.println(">>>>>>>>>>>>>>" + loginBtn.asXml());
        //模拟点击登录按钮
        newPage = loginBtn.click();

        /*
         * 如果出现htmlunit click无法跳转、js方法執行windows.location.href跳转后无法获取跳转后的页面等问题可以使用下面两个方案
         *
         * 方案1
         * Page nextPage =  newPage.getEnclosingWindow().getTopWindow().getEnclosedPage();
         *
         *方案2
         * 等待js执行完成，循环20次，每次等待5秒知道加载完成
         *   for (int i = 0; i < 20; i++) {
         *    if () {
         *        break;condition_to_happen_after_js_execution
         *   }
         *   synchronized (page) {
         *        page.wait(5000);
         *   }
         *  }*/
        return newPage;
    }

    /**
     * 爬取主要数据，需要用户自定义实现
     *
     * @return
     */
//    public abstract String spider(LoginParam loginParam);

    public InputStream getCheckCodeImage(LoginParam loginParam) throws IOException {
        //给webClient设置参数
        WebClient webClient = load(loginParam);
        //保存当前的WebClient，用户来获取登录后的Cookies
        webClientMaps.put(loginParam.getLoginUrl(), webClient);
        //获取登录页面
        HtmlPage loginPage = webClient.getPage(loginParam.getLoginUrl());
        //保存当前登录页面，在login方法中会用到
        loginPageMaps.put(loginParam.getLoginUrl(), loginPage);
        //获取登录页面中图片验证码
//        System.out.println(">>>>>>>>>>>>>>>>>>" + loginPage.asXml());
        HtmlImage image = (HtmlImage) loginPage.getElementById(loginParam.getCheckCodeImgId());
        //图片验证码字节输入流,这里的参数一定要是true,就是下载这个图片，如果不下载是不能转换成输入流的
        System.out.println(">>>>>>>>>>>>>>>>>>" + image.asXml());

        return image.getWebResponse(true).getContentAsStream();
    }
}
